﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace Creventive.Wpf.ShellFactory.Utility
{
    /// <summary>
    ///   Provides collection synchronization functions.
    /// </summary>
    public static class CollectionSynchronizerExtension
    {
        /// <summary>
        ///   Synchronizes the specified target collection with the specified source collection.
        /// </summary>
        /// <typeparam name = "T"></typeparam>
        /// <param name = "target">The target.</param>
        /// <param name = "source">The source.</param>
        /// <param name = "equalityComparer">The equality comparer.</param>
        /// <param name = "flags">The flags.</param>
        /// <returns></returns>
        public static bool Synchronize<T>(this IList<T> target, IList<T> source, IEqualityComparer<T> equalityComparer, CollectionSynchronizeFlags flags)
        {
            if (equalityComparer == null)
                equalityComparer = EqualityComparer<T>.Default;

#if DEBUG
            VerifyNoDuplicates(target, equalityComparer, "target");
            VerifyNoDuplicates(source, equalityComparer, "source");
#endif

            T[] sharedItems = target.Intersect(source, equalityComparer).ToArray();
            T[] deletedItems = target.Where(e => !sharedItems.Contains(e, equalityComparer)).ToArray();
            T[] insertedItems = source.Where(e => !sharedItems.Contains(e, equalityComparer)).ToArray();

            bool autoDispose = (flags & CollectionSynchronizeFlags.AutoDispose) == CollectionSynchronizeFlags.AutoDispose;

            // Remove deleted items
            foreach (var deletedItem in deletedItems)
            {
                target.Remove(deletedItem);
                if (autoDispose && (deletedItem is IDisposable))
                    ((IDisposable)deletedItem).Dispose();
            }

            // Move items which have changed position
            ObservableCollection<T> observableTarget = target as ObservableCollection<T>;
            for (int destinationIndex = 0; destinationIndex < sharedItems.Length; destinationIndex++)
            {
                T item = sharedItems[destinationIndex];
                int originalIndex = target.IndexOf(item);
                if (originalIndex != destinationIndex)
                {
                    if (observableTarget != null)
                        observableTarget.Move(originalIndex, destinationIndex);
                    else
                    {
                        target.RemoveAt(originalIndex);
                        target.Insert(destinationIndex, item);
                    }
                }
            }

            // Insert new items
            foreach (var insertedItem in insertedItems)
            {
                int idx = source.IndexOf(insertedItem, equalityComparer);
                target.Insert(idx, insertedItem);
            }

            return true;
        }

#if DEBUG
        private static void VerifyNoDuplicates<T>(IEnumerable<T> target, IEqualityComparer<T> equalityComparer, string argumentName)
        {
            if (target.Distinct(equalityComparer).Count() < target.Count())
                throw new ArgumentException(@"Cannot synchronize a collection which contains duplicate entries of the same object.", argumentName);
        }
#endif

        /// <summary>
        ///   Synchronizes the specified target collection with the specified source collection.
        /// </summary>
        /// <typeparam name = "T"></typeparam>
        /// <param name = "target">The target.</param>
        /// <param name = "source">The source.</param>
        /// <param name = "flags">The flags.</param>
        public static void Synchronize<T>(this IList<T> target, IList<T> source, CollectionSynchronizeFlags flags)
        {
            Synchronize(target, source, EqualityComparer<T>.Default, flags);
        }

        /// <summary>
        ///   Synchronizes the specified target collection with the specified source collection.
        /// </summary>
        /// <typeparam name = "T"></typeparam>
        /// <param name = "target">The target.</param>
        /// <param name = "source">The source.</param>
        public static void Synchronize<T>(this IList<T> target, IList<T> source)
        {
            Synchronize(target, source, EqualityComparer<T>.Default, CollectionSynchronizeFlags.None);
        }
    }
}